/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/extensions/sync.h>
#include <X11/extensions/Xinerama.h>
#include <glib.h>
#include <libxml/xinclude.h>
#include "../nls.h"
#include "../toolkit/display.h"
#include "../toolkit/xqueue.h"
#include "../toolkit/prop.h"
#include "../toolkit/xml.h"
#include "misc.h"
#include "wm.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "misc.h"
#include "client.h"
#include "event.h"
#include "screen.h"
#include "config.h"
#include "focus.h"
#include "frame.h"
#include "debug.h"
#include "moveresize.h"
#include "focus_cycle.h"
#include "place.h"
#define ROOT_EVENTMASK (StructureNotifyMask | PropertyChangeMask | EnterWindowMask | LeaveWindowMask | SubstructureRedirectMask | FocusChangeMask |  ButtonPressMask | ButtonReleaseMask)
guint screen_num_desktops;
guint screen_num_monitors;
guint screen_desktop;
guint screen_last_desktop;
gboolean screen_showing_desktop;
struct wm_desktop_layout screen_desktop_layout;
Window screen_support_win;
Time screen_desktop_user_time = CurrentTime;
static struct wm_size screen_physical_size;
static guint screen_old_desktop;
static gboolean screen_desktop_timeout = TRUE;
static guint screen_desktop_timer = 0;
static struct wm_rect *monitor_area = NULL;
static GSList *struts_top = NULL;
static GSList *struts_left = NULL;
static GSList *struts_right = NULL;
static GSList *struts_bottom = NULL;
#define REMEMBER_LAST_DESKTOP_TIME 750
static gboolean replace_wm(void)
{
	gchar *wm_sn;
	Atom wm_sn_atom;
	Window current_wm_sn_owner;
	Time timestamp;
	wm_sn = g_strdup_printf("WM_S%d", DefaultScreen(t_display));
	wm_sn_atom = XInternAtom(t_display, wm_sn, FALSE);
	g_free(wm_sn);
	current_wm_sn_owner = XGetSelectionOwner(t_display, wm_sn_atom);
	if(current_wm_sn_owner == screen_support_win)
		current_wm_sn_owner = None;
	if(current_wm_sn_owner) {
		if(!wm_replace) {
			g_message(_("A window manager is already running on screen %d"), DefaultScreen(t_display));
			return FALSE;
		}
		t_display_ignore_errors(TRUE);
		XSelectInput(t_display, current_wm_sn_owner, StructureNotifyMask);
		XSync(t_display, FALSE);
		t_display_ignore_errors(FALSE);
		if(t_display_error_occured)
			current_wm_sn_owner = None;
	}
	timestamp = event_time();
	XSetSelectionOwner(t_display, wm_sn_atom, screen_support_win, timestamp);
	if(XGetSelectionOwner(t_display, wm_sn_atom) != screen_support_win) {
		g_message(_("Could not acquire window manager selection on screen %d"), DefaultScreen(t_display));
		return FALSE;
	}
	if(current_wm_sn_owner) {
		gulong wait = 0;
		const gulong timeout = G_USEC_PER_SEC * 15;
		struct wm_xqueue_window_type wt;
		wt.window = current_wm_sn_owner;
		wt.type = DestroyNotify;
		while(wait < timeout) {
			if(xqueue_exists_local(xqueue_match_window_type, &wt))
				break;
			g_usleep(G_USEC_PER_SEC / 10);
			wait += G_USEC_PER_SEC / 10;
		}
		if(wait >= timeout) {
			g_message(_("The WM on screen %d is not exiting"), DefaultScreen(t_display));
			return FALSE;
		}
	}
	t_prop_message(DefaultScreen(t_display), DefaultRootWindow(t_display), T_PROP_ATOM(MANAGER), timestamp, wm_sn_atom,
		screen_support_win, 0, 0, SubstructureNotifyMask);
	return TRUE;
}

gboolean screen_annex(void)
{
	XSetWindowAttributes attrib;
	pid_t pid;
	gint i, num_support;
	gulong *supported;
	attrib.override_redirect = TRUE;
	attrib.event_mask = PropertyChangeMask;
	screen_support_win =
		XCreateWindow(t_display, DefaultRootWindow(t_display), -100, -100, 1, 1, 0, CopyFromParent, InputOutput,
		CopyFromParent, CWEventMask | CWOverrideRedirect, &attrib);
	XMapWindow(t_display, screen_support_win);
	XLowerWindow(t_display, screen_support_win);
	if(!replace_wm()) {
		XDestroyWindow(t_display, screen_support_win);
		return FALSE;
	}
	t_display_ignore_errors(TRUE);
	XSelectInput(t_display, DefaultRootWindow(t_display), ROOT_EVENTMASK);
	t_display_ignore_errors(FALSE);
	if(t_display_error_occured) {
		g_message(_("A window manager is already running on screen %d"), DefaultScreen(t_display));
		XDestroyWindow(t_display, screen_support_win);
		return FALSE;
	}
	XDefineCursor(t_display, DefaultRootWindow(t_display), wm_cursor(WM_CURSOR_POINTER));
	pid = getpid();
	T_PROP_SET32(DefaultRootWindow(t_display), LBOXWM_PID, CARDINAL, pid);
	num_support = T_PROP_NUM_ATOMS - T_PROP_NET_SUPPORTED - 1;
	i = 0;
	supported = g_new(gulong, num_support);
	supported[i++] = T_PROP_ATOM(NET_WM_FULL_PLACEMENT);
	supported[i++] = T_PROP_ATOM(NET_WM_SYNC_REQUEST);
	supported[i++] = T_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER);
	supported[i++] = T_PROP_ATOM(LBOXWM_PID);
	supported[i++] = T_PROP_ATOM(LBOXWM_CONFIG_FILE);
	supported[i++] = T_PROP_ATOM(LBOXWM_VERSION);
	supported[i++] = T_PROP_ATOM(LBOXWM_APP_TITLE);
	supported[i++] = T_PROP_ATOM(LBOXWM_APP_NAME);
	supported[i++] = T_PROP_ATOM(LBOXWM_APP_CLASS);
	supported[i++] = T_PROP_ATOM(LBOXWM_APP_TYPE);
	g_assert(i == num_support);
	T_PROP_SETA32(DefaultRootWindow(t_display), NET_SUPPORTED, ATOM, supported, num_support);
	g_free(supported);
	T_PROP_SETS(DefaultRootWindow(t_display), LBOXWM_VERSION, PACKAGE_VERSION);
	return TRUE;
}

static void update_layout(void)
{
	screen_desktop_layout.orientation = WM_ORIENTATION_HORZ;
	screen_desktop_layout.start_corner = WM_CORNER_TOPLEFT;
	screen_desktop_layout.rows = 1;
	screen_desktop_layout.columns = screen_num_desktops;
}

static void set_num_desktops(guint num)
{
	GList *it, *stacking_copy;
	g_assert(num > 0);
	if(screen_num_desktops == num)
		return;
	screen_num_desktops = num;
	update_layout();
	stacking_copy = g_list_copy(stacking_list);
	for(it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			if(c->desktop != DESKTOP_ALL && c->desktop >= num)
				client_set_desktop(c, num - 1, FALSE, TRUE);
			else if(screen_desktop == num - 1 && (c->desktop == DESKTOP_ALL || c->desktop == screen_desktop))
				stacking_raise(CLIENT_AS_WINDOW(c));
		}
	}
	g_list_free(stacking_copy);
	screen_update_areas();
	if(screen_desktop >= screen_num_desktops)
		screen_set_desktop(num - 1, TRUE);
}

void screen_startup(void)
{
	screen_resize();
	screen_num_desktops = 0;
	set_num_desktops(config_desktops_num);
	screen_desktop = screen_num_desktops;
	screen_set_desktop(MIN(config_screen_firstdesk, screen_num_desktops) - 1, FALSE);
	screen_last_desktop = screen_desktop;
	screen_showing_desktop = FALSE;
	update_layout();
}

void screen_shutdown(void)
{
	XSelectInput(t_display, DefaultRootWindow(t_display), NoEventMask);
	T_PROP_ERASE(DefaultRootWindow(t_display), LBOXWM_PID);
	T_PROP_ERASE(DefaultRootWindow(t_display), NET_SUPPORTED);
	XDestroyWindow(t_display, screen_support_win);
}

void screen_resize(void)
{
	static gint oldw = 0, oldh = 0;
	gint w, h;
	GList *it;
	w = WidthOfScreen(DefaultScreenOfDisplay(t_display));
	h = HeightOfScreen(DefaultScreenOfDisplay(t_display));
	if(w == oldw && h == oldh)
		return;
	oldw = w;
	oldh = h;
	screen_physical_size.width = w;
	screen_physical_size.height = h;
	if(wm_state() != WM_STATE_RUNNING)
		return;
	if(oldw)
		for(it = client_list; it; it = g_list_next(it))
			client_move_onscreen(it->data, FALSE);
}

static void screen_fallback_focus(void)
{
	struct wm_client *c;
	gboolean allow_omni;
	allow_omni = focus_client && (client_normal(focus_client) && focus_client->desktop == DESKTOP_ALL);
	if(focus_client && focus_client->desktop == screen_desktop)
		return;
	if((c = focus_fallback(TRUE, !config_focus_last, allow_omni, !allow_omni))) {
		if(c->can_focus) {
			frame_adjust_focus(c->frame, TRUE);
			client_bring_helper_windows(c);
		}
	}
}

static gboolean last_desktop_func(gpointer data)
{
	(void)data;
	screen_desktop_timeout = TRUE;
	screen_desktop_timer = 0;
	return FALSE;
}

void screen_set_desktop(guint num, gboolean dofocus)
{
	GList *it;
	guint previous;
	gulong ignore_start;
	g_assert(num < screen_num_desktops);
	previous = screen_desktop;
	screen_desktop = num;
	if(previous == num)
		return;
	if(screen_desktop_timeout) {
		if(screen_last_desktop == previous)
			screen_old_desktop = screen_desktop;
		else {
			screen_old_desktop = screen_last_desktop;
			screen_last_desktop = previous;
		}
	} else {
		if(screen_desktop == screen_last_desktop) {
			if(previous == screen_old_desktop) {
				screen_last_desktop = screen_old_desktop;
			} else if(screen_last_desktop == screen_old_desktop) {
				screen_last_desktop = previous;
			} else {
				screen_last_desktop = screen_old_desktop;
			}
		} else {
			if(screen_desktop == screen_old_desktop) {
			} else if(previous == screen_old_desktop) {
			} else if(screen_last_desktop == screen_old_desktop) {
				screen_last_desktop = previous;
			} else {
			}
		}
	}
	screen_desktop_timeout = FALSE;
	if(screen_desktop_timer)
		g_source_remove(screen_desktop_timer);
	screen_desktop_timer = g_timeout_add(REMEMBER_LAST_DESKTOP_TIME, last_desktop_func, NULL);
	wm_debug("Moving to desktop %d", num + 1);
	ignore_start = event_start_ignore_all_enters();
	if(moveresize_client)
		client_set_desktop(moveresize_client, num, TRUE, FALSE);
	for(it = stacking_list; it; it = g_list_next(it)) {
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			client_show(c);
		}
	} if(dofocus)
		screen_fallback_focus();
	for(it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			if(client_hide(c)) {
				if(c == focus_client) {
					focus_set_client(NULL);
				}
			}
		}
	}
	focus_cycle_addremove(NULL, TRUE);
	event_end_ignore_all_enters(ignore_start);
	if(event_source_time() != CurrentTime)
		screen_desktop_user_time = event_source_time();
}

void screen_add_desktop(gboolean current)
{
	gulong ignore_start;
	ignore_start = event_start_ignore_all_enters();
	set_num_desktops(screen_num_desktops + 1);
	if(current) {
		GList *it;
		for(it = client_list; it; it = g_list_next(it)) {
			struct wm_client *c = it->data;
			if(c->desktop != DESKTOP_ALL && c->desktop >= screen_desktop && !client_direct_parent(c)) {
				wm_debug("moving window %s", c->title);
				client_set_desktop(c, c->desktop + 1, FALSE, TRUE);
			}
		}
	}
	event_end_ignore_all_enters(ignore_start);
}

void screen_remove_desktop(gboolean current)
{
	guint rmdesktop, movedesktop;
	GList *it, *stacking_copy;
	gulong ignore_start;
	if(screen_num_desktops <= 1)
		return;
	ignore_start = event_start_ignore_all_enters();
	if(current)
		rmdesktop = screen_desktop;
	else
		rmdesktop = screen_num_desktops - 1;
	if(rmdesktop < screen_num_desktops - 1)
		movedesktop = rmdesktop + 1;
	else
		movedesktop = rmdesktop;
	stacking_copy = g_list_copy(stacking_list);
	for(it = g_list_last(stacking_copy); it; it = g_list_previous(it)) {
		if(WINDOW_IS_CLIENT(it->data)) {
			struct wm_client *c = it->data;
			guint d = c->desktop;
			if(d != DESKTOP_ALL && d >= movedesktop && !client_direct_parent(c)) {
				wm_debug("moving window %s", c->title);
				client_set_desktop(c, c->desktop - 1, TRUE, TRUE);
			}
			if((screen_desktop == rmdesktop - 1 || screen_desktop == rmdesktop) && (d == DESKTOP_ALL || d == screen_desktop)) {
				stacking_raise(CLIENT_AS_WINDOW(c));
				wm_debug("raising window %s", c->title);
			}
		}
	}
	g_list_free(stacking_copy);
	if(screen_desktop < screen_num_desktops - 1) {
		screen_fallback_focus();
		wm_debug("fake desktop change");
	}
	set_num_desktops(screen_num_desktops - 1);
	event_end_ignore_all_enters(ignore_start);
}

static void get_row_col(guint d, guint *r, guint *c)
{
	switch (screen_desktop_layout.orientation) {
	case WM_ORIENTATION_HORZ:
		switch (screen_desktop_layout.start_corner) {
		case WM_CORNER_TOPLEFT:
			*r = d / screen_desktop_layout.columns;
			*c = d % screen_desktop_layout.columns;
			break;
		case WM_CORNER_BOTTOMLEFT:
			*r = screen_desktop_layout.rows - 1 - d / screen_desktop_layout.columns;
			*c = d % screen_desktop_layout.columns;
			break;
		case WM_CORNER_TOPRIGHT:
			*r = d / screen_desktop_layout.columns;
			*c = screen_desktop_layout.columns - 1 - d % screen_desktop_layout.columns;
			break;
		case WM_CORNER_BOTTOMRIGHT:
			*r = screen_desktop_layout.rows - 1 - d / screen_desktop_layout.columns;
			*c = screen_desktop_layout.columns - 1 - d % screen_desktop_layout.columns;
			break;
		}
		break;
	case WM_ORIENTATION_VERT:
		switch (screen_desktop_layout.start_corner) {
		case WM_CORNER_TOPLEFT:
			*r = d % screen_desktop_layout.rows;
			*c = d / screen_desktop_layout.rows;
			break;
		case WM_CORNER_BOTTOMLEFT:
			*r = screen_desktop_layout.rows - 1 - d % screen_desktop_layout.rows;
			*c = d / screen_desktop_layout.rows;
			break;
		case WM_CORNER_TOPRIGHT:
			*r = d % screen_desktop_layout.rows;
			*c = screen_desktop_layout.columns - 1 - d / screen_desktop_layout.rows;
			break;
		case WM_CORNER_BOTTOMRIGHT:
			*r = screen_desktop_layout.rows - 1 - d % screen_desktop_layout.rows;
			*c = screen_desktop_layout.columns - 1 - d / screen_desktop_layout.rows;
			break;
		}
		break;
	}
}

static guint translate_row_col(guint r, guint c)
{
	switch (screen_desktop_layout.orientation) {
	case WM_ORIENTATION_HORZ:
		switch (screen_desktop_layout.start_corner) {
		case WM_CORNER_TOPLEFT:
			return r % screen_desktop_layout.rows * screen_desktop_layout.columns + c % screen_desktop_layout.columns;
		case WM_CORNER_BOTTOMLEFT:
			return (screen_desktop_layout.rows - 1 - r % screen_desktop_layout.rows) * screen_desktop_layout.columns +
				c % screen_desktop_layout.columns;
		case WM_CORNER_TOPRIGHT:
			return r % screen_desktop_layout.rows * screen_desktop_layout.columns + (screen_desktop_layout.columns - 1 -
				c % screen_desktop_layout.columns);
		case WM_CORNER_BOTTOMRIGHT:
			return (screen_desktop_layout.rows - 1 - r % screen_desktop_layout.rows) * screen_desktop_layout.columns +
				(screen_desktop_layout.columns - 1 - c % screen_desktop_layout.columns);
		}
	case WM_ORIENTATION_VERT:
		switch (screen_desktop_layout.start_corner) {
		case WM_CORNER_TOPLEFT:
			return c % screen_desktop_layout.columns * screen_desktop_layout.rows + r % screen_desktop_layout.rows;
		case WM_CORNER_BOTTOMLEFT:
			return c % screen_desktop_layout.columns * screen_desktop_layout.rows + (screen_desktop_layout.rows - 1 -
				r % screen_desktop_layout.rows);
		case WM_CORNER_TOPRIGHT:
			return (screen_desktop_layout.columns - 1 - c % screen_desktop_layout.columns) * screen_desktop_layout.rows +
				r % screen_desktop_layout.rows;
		case WM_CORNER_BOTTOMRIGHT:
			return (screen_desktop_layout.columns - 1 - c % screen_desktop_layout.columns) * screen_desktop_layout.rows +
				(screen_desktop_layout.rows - 1 - r % screen_desktop_layout.rows);
		}
	}
	g_assert_not_reached();
	return 0;
}

guint screen_find_desktop(guint from, enum wm_direction dir, gboolean wrap, gboolean linear)
{
	guint r, c;
	guint d;
	d = from;
	get_row_col(d, &r, &c);
	if(linear) {
		switch (dir) {
		case WM_DIRECTION_EAST:
			if(d < screen_num_desktops - 1)
				++d;
			else if(wrap)
				d = 0;
			else
				return from;
			break;
		case WM_DIRECTION_WEST:
			if(d > 0)
				--d;
			else if(wrap)
				d = screen_num_desktops - 1;
			else
				return from;
			break;
		default:
			g_assert_not_reached();
			return from;
		}
	} else {
		switch (dir) {
		case WM_DIRECTION_EAST:
			++c;
			if(c >= screen_desktop_layout.columns) {
				if(wrap)
					c = 0;
				else
					return from;
			}
			d = translate_row_col(r, c);
			if(d >= screen_num_desktops) {
				if(wrap)
					++c;
				else
					return from;
			}
			break;
		case WM_DIRECTION_WEST:
			--c;
			if(c >= screen_desktop_layout.columns) {
				if(wrap)
					c = screen_desktop_layout.columns - 1;
				else
					return from;
			}
			d = translate_row_col(r, c);
			if(d >= screen_num_desktops) {
				if(wrap)
					--c;
				else
					return from;
			}
			break;
		case WM_DIRECTION_SOUTH:
			++r;
			if(r >= screen_desktop_layout.rows) {
				if(wrap)
					r = 0;
				else
					return from;
			}
			d = translate_row_col(r, c);
			if(d >= screen_num_desktops) {
				if(wrap)
					++r;
				else
					return from;
			}
			break;
		case WM_DIRECTION_NORTH:
			--r;
			if(r >= screen_desktop_layout.rows) {
				if(wrap)
					r = screen_desktop_layout.rows - 1;
				else
					return from;
			}
			d = translate_row_col(r, c);
			if(d >= screen_num_desktops) {
				if(wrap)
					--r;
				else
					return from;
			}
			break;
		default:
			g_assert_not_reached();
			return from;
		}
		d = translate_row_col(r, c);
	}
	return d;
}

void screen_show_desktop(gboolean show, struct wm_client *show_only)
{
	GList *it;
	if(show == screen_showing_desktop)
		return;
	screen_showing_desktop = show;
	if(show) {
		for(it = g_list_last(stacking_list); it; it = g_list_previous(it)) {
			if(WINDOW_IS_CLIENT(it->data)) {
				struct wm_client *client = it->data;
				client_showhide(client);
			}
		}
	} else {
		for(it = stacking_list; it; it = g_list_next(it)) {
			if(WINDOW_IS_CLIENT(it->data)) {
				struct wm_client *client = it->data;
				if(client_should_show(client)) {
					if(!show_only || client == show_only)
						client_show(client);
				}
			}
		}
	}
	if(show) {
		for(it = focus_order; it; it = g_list_next(it)) {
			struct wm_client *c = it->data;
			if(c->type == WM_CLIENT_TYPE_DESKTOP && (c->desktop == screen_desktop || c->desktop == DESKTOP_ALL)
				&& client_focus(it->data))
				break;
		}
	} else if(!show_only) {
		struct wm_client *c;
		if((c = focus_fallback(TRUE, FALSE, TRUE, FALSE))) {
			if(c->can_focus) {
				frame_adjust_focus(c->frame, TRUE);
			}
		}
	}
	show = ! !show;
}

void screen_install_colormap(struct wm_client *client, gboolean install)
{
	if(client == NULL || client->colormap == None) {
		if(install)
			XInstallColormap(t_display, DefaultColormap(t_display, DefaultScreen(t_display)));
		else
			XUninstallColormap(t_display, DefaultColormap(t_display, DefaultScreen(t_display)));
	} else {
		t_display_ignore_errors(TRUE);
		if(install)
			XInstallColormap(t_display, client->colormap);
		else
			XUninstallColormap(t_display, client->colormap);
		t_display_ignore_errors(FALSE);
	}
}

struct wm_screen_strut {
	guint desktop;
	struct strut_partial *strut;
};
#define RESET_STRUT_LIST(sl) \
    while (sl) { \
        g_slice_free(struct wm_screen_strut, (sl)->data); \
        sl = g_slist_delete_link(sl, sl); \
    }
#define ADD_STRUT_TO_LIST(sl, d, s) \
{ \
    struct wm_screen_strut *ss = g_slice_new(struct wm_screen_strut); \
    ss->desktop = d; \
    ss->strut = s;  \
    sl = g_slist_prepend(sl, ss); \
}
#define VALIDATE_STRUTS(sl, side, max) \
{ \
    GSList *it; \
    for (it = sl; it; it = g_slist_next(it)) { \
      struct wm_screen_strut *ss = it->data; \
      ss->strut->side = MIN(max, ss->strut->side); \
    } \
}
static void get_xinerama_screens(struct wm_rect **xin_areas, guint *nxin)
{
	guint i;
	gint n, l, r, t, b;
	XineramaScreenInfo *info;
	if(wm_debug_xinerama) {
		gint w = WidthOfScreen(DefaultScreenOfDisplay(t_display));
		gint h = HeightOfScreen(DefaultScreenOfDisplay(t_display));
		*nxin = 2;
		*xin_areas = g_new(struct wm_rect, *nxin + 1);
		RECT_SET((*xin_areas)[0], 0, 0, w / 2, h);
		RECT_SET((*xin_areas)[1], w / 2, 0, w - (w / 2), h);
	} else if(t_display_extension_xinerama && (info = XineramaQueryScreens(t_display, &n))) {
		*nxin = n;
		*xin_areas = g_new(struct wm_rect, *nxin + 1);
		for(i = 0; i < *nxin; ++i)
			RECT_SET((*xin_areas)[i], info[i].x_org, info[i].y_org, info[i].width, info[i].height);
		XFree(info);
	} else {
		*nxin = 1;
		*xin_areas = g_new(struct wm_rect, *nxin + 1);
		RECT_SET((*xin_areas)[0], 0, 0, WidthOfScreen(DefaultScreenOfDisplay(t_display)),
			HeightOfScreen(DefaultScreenOfDisplay(t_display)));
	} l = (*xin_areas)[0].x;
	t = (*xin_areas)[0].y;
	r = (*xin_areas)[0].x + (*xin_areas)[0].width - 1;
	b = (*xin_areas)[0].y + (*xin_areas)[0].height - 1;
	for(i = 1; i < *nxin; ++i) {
		l = MIN(l, (*xin_areas)[i].x);
		t = MIN(l, (*xin_areas)[i].y);
		r = MAX(r, (*xin_areas)[i].x + (*xin_areas)[i].width - 1);
		b = MAX(b, (*xin_areas)[i].y + (*xin_areas)[i].height - 1);
	}
	RECT_SET((*xin_areas)[*nxin], l, t, r - l + 1, b - t + 1);
}

void screen_update_areas(void)
{
	GList *it, *onscreen;
	onscreen = NULL;
	for(it = client_list; it; it = g_list_next(it)) {
		if(client_monitor(it->data) != screen_num_monitors)
			onscreen = g_list_prepend(onscreen, it->data);
	}
	g_free(monitor_area);
	get_xinerama_screens(&monitor_area, &screen_num_monitors);
	RESET_STRUT_LIST(struts_left);
	RESET_STRUT_LIST(struts_top);
	RESET_STRUT_LIST(struts_right);
	RESET_STRUT_LIST(struts_bottom);
	for(it = client_list; it; it = g_list_next(it)) {
		struct wm_client *c = it->data;
		if(c->strut.left)
			ADD_STRUT_TO_LIST(struts_left, c->desktop, &c->strut);
		if(c->strut.top)
			ADD_STRUT_TO_LIST(struts_top, c->desktop, &c->strut);
		if(c->strut.right)
			ADD_STRUT_TO_LIST(struts_right, c->desktop, &c->strut);
		if(c->strut.bottom)
			ADD_STRUT_TO_LIST(struts_bottom, c->desktop, &c->strut);
	}
	VALIDATE_STRUTS(struts_left, left, monitor_area[screen_num_monitors].width / 2);
	VALIDATE_STRUTS(struts_right, right, monitor_area[screen_num_monitors].width / 2);
	VALIDATE_STRUTS(struts_top, top, monitor_area[screen_num_monitors].height / 2);
	VALIDATE_STRUTS(struts_bottom, bottom, monitor_area[screen_num_monitors].height / 2);
	for(it = onscreen; it; it = g_list_next(it))
		client_reconfigure(it->data, FALSE);
}

#define STRUT_LEFT_IN_SEARCH(s, search) \
    (RANGES_INTERSECT(search->y, search->height, \
                      s->left_start, s->left_end - s->left_start + 1))
#define STRUT_RIGHT_IN_SEARCH(s, search) \
    (RANGES_INTERSECT(search->y, search->height, \
                      s->right_start, s->right_end - s->right_start + 1))
#define STRUT_TOP_IN_SEARCH(s, search) \
    (RANGES_INTERSECT(search->x, search->width, \
                      s->top_start, s->top_end - s->top_start + 1))
#define STRUT_BOTTOM_IN_SEARCH(s, search) \
    (RANGES_INTERSECT(search->x, search->width, \
                      s->bottom_start, s->bottom_end - s->bottom_start + 1))
#define STRUT_LEFT_IGNORE(s, us, search) \
    (head == SCREEN_AREA_ALL_MONITORS && us && \
     RECT_LEFT(monitor_area[i]) + s->left > RECT_LEFT(*search))
#define STRUT_RIGHT_IGNORE(s, us, search) \
    (head == SCREEN_AREA_ALL_MONITORS && us && \
     RECT_RIGHT(monitor_area[i]) - s->right < RECT_RIGHT(*search))
#define STRUT_TOP_IGNORE(s, us, search) \
    (head == SCREEN_AREA_ALL_MONITORS && us && \
     RECT_TOP(monitor_area[i]) + s->top > RECT_TOP(*search))
#define STRUT_BOTTOM_IGNORE(s, us, search) \
    (head == SCREEN_AREA_ALL_MONITORS && us && \
     RECT_BOTTOM(monitor_area[i]) - s->bottom < RECT_BOTTOM(*search))
struct wm_rect *screen_area(guint desktop, guint head, struct wm_rect *search)
{
	struct wm_rect *a;
	GSList *it;
	gint l, r, t, b;
	guint i, d;
	gboolean us = search != NULL;
	g_assert(desktop < screen_num_desktops || desktop == DESKTOP_ALL);
	g_assert(head < screen_num_monitors || head == SCREEN_AREA_ONE_MONITOR || head == SCREEN_AREA_ALL_MONITORS);
	g_assert(!(head == SCREEN_AREA_ONE_MONITOR && search == NULL));
	if(!search) {
		if(head < screen_num_monitors)
			search = &monitor_area[head];
		else
			search = &monitor_area[screen_num_monitors];
	}
	if(head == SCREEN_AREA_ONE_MONITOR)
		head = screen_find_monitor(search);
	if(RECT_INTERSECTS_RECT(monitor_area[screen_num_monitors], *search)) {
		l = RECT_RIGHT(monitor_area[screen_num_monitors]);
		t = RECT_BOTTOM(monitor_area[screen_num_monitors]);
		r = RECT_LEFT(monitor_area[screen_num_monitors]);
		b = RECT_TOP(monitor_area[screen_num_monitors]);
		for(i = 0; i < screen_num_monitors; ++i) {
			if(RANGES_INTERSECT(search->x, search->width, monitor_area[i].x, monitor_area[i].width)) {
				t = MIN(t, RECT_TOP(monitor_area[i]));
				b = MAX(b, RECT_BOTTOM(monitor_area[i]));
			}
			if(RANGES_INTERSECT(search->y, search->height, monitor_area[i].y, monitor_area[i].height)) {
				l = MIN(l, RECT_LEFT(monitor_area[i]));
				r = MAX(r, RECT_RIGHT(monitor_area[i]));
			}
		}
	} else {
		l = RECT_LEFT(monitor_area[screen_num_monitors]);
		t = RECT_TOP(monitor_area[screen_num_monitors]);
		r = RECT_RIGHT(monitor_area[screen_num_monitors]);
		b = RECT_BOTTOM(monitor_area[screen_num_monitors]);
	}
	for(d = 0; d < screen_num_desktops; ++d) {
		if(d != desktop && desktop != DESKTOP_ALL)
			continue;
		for(i = 0; i < screen_num_monitors; ++i) {
			if(head != SCREEN_AREA_ALL_MONITORS && head != i)
				continue;
			for(it = struts_left; it; it = g_slist_next(it)) {
				struct wm_screen_strut *s = it->data;
				if((s->desktop == d || s->desktop == DESKTOP_ALL) && STRUT_LEFT_IN_SEARCH(s->strut, search)
					&& !STRUT_LEFT_IGNORE(s->strut, us, search))
					l = MAX(l, RECT_LEFT(monitor_area[screen_num_monitors]) + s->strut->left);
			}
			for(it = struts_top; it; it = g_slist_next(it)) {
				struct wm_screen_strut *s = it->data;
				if((s->desktop == d || s->desktop == DESKTOP_ALL) && STRUT_TOP_IN_SEARCH(s->strut, search)
					&& !STRUT_TOP_IGNORE(s->strut, us, search))
					t = MAX(t, RECT_TOP(monitor_area[screen_num_monitors]) + s->strut->top);
			}
			for(it = struts_right; it; it = g_slist_next(it)) {
				struct wm_screen_strut *s = it->data;
				if((s->desktop == d || s->desktop == DESKTOP_ALL) && STRUT_RIGHT_IN_SEARCH(s->strut, search)
					&& !STRUT_RIGHT_IGNORE(s->strut, us, search))
					r = MIN(r, RECT_RIGHT(monitor_area[screen_num_monitors]) - s->strut->right);
			}
			for(it = struts_bottom; it; it = g_slist_next(it)) {
				struct wm_screen_strut *s = it->data;
				if((s->desktop == d || s->desktop == DESKTOP_ALL) && STRUT_BOTTOM_IN_SEARCH(s->strut, search)
					&& !STRUT_BOTTOM_IGNORE(s->strut, us, search))
					b = MIN(b, RECT_BOTTOM(monitor_area[screen_num_monitors]) - s->strut->bottom);
			}
			if(head == i) {
				l = MAX(l, RECT_LEFT(monitor_area[i]));
				t = MAX(t, RECT_TOP(monitor_area[i]));
				r = MIN(r, RECT_RIGHT(monitor_area[i]));
				b = MIN(b, RECT_BOTTOM(monitor_area[i]));
			}
		}
	}
	a = g_slice_new(struct wm_rect);
	a->x = l;
	a->y = t;
	a->width = r - l + 1;
	a->height = b - t + 1;
	return a;
}

guint screen_find_monitor(const struct wm_rect *search)
{
	guint i;
	guint most = screen_num_monitors;
	guint mostv = 0;
	for(i = 0; i < screen_num_monitors; ++i) {
		const struct wm_rect *area = screen_physical_area_monitor(i);
		if(RECT_INTERSECTS_RECT(*area, *search)) {
			struct wm_rect r;
			guint v;
			RECT_SET_INTERSECTION(r, *area, *search);
			v = r.width * r.height;
			if(v > mostv) {
				mostv = v;
				most = i;
			}
		}
	}
	return most < screen_num_monitors ? most : screen_monitor_primary(FALSE);
}

const struct wm_rect *screen_physical_area_all_monitors(void)
{
	return screen_physical_area_monitor(screen_num_monitors);
}

const struct wm_rect *screen_physical_area_monitor(guint head)
{
	g_assert(head <= screen_num_monitors);
	return &monitor_area[head];
}

gboolean screen_physical_area_monitor_contains(guint head, struct wm_rect *search)
{
	g_assert(head <= screen_num_monitors);
	g_assert(search);
	return RECT_INTERSECTS_RECT(monitor_area[head], *search);
}

static guint monitor_active(void)
{
	if(moveresize_client)
		return client_monitor(moveresize_client);
	else if(focus_client)
		return client_monitor(focus_client);
	else
		return screen_monitor_pointer();
}

const struct wm_rect *screen_physical_area_active(void)
{
	return screen_physical_area_monitor(monitor_active());
}

guint screen_monitor_primary(gboolean fixed)
{
	if(config_primary_monitor_index > 0) {
		if(config_primary_monitor_index - 1 < screen_num_monitors)
			return config_primary_monitor_index - 1;
		else
			return 0;
	} else if(fixed)
		return 0;
	else if(config_primary_monitor == WM_PLACE_MONITOR_ACTIVE)
		return monitor_active();
	else
		return screen_monitor_pointer();
}

const struct wm_rect *screen_physical_area_primary(gboolean fixed)
{
	return screen_physical_area_monitor(screen_monitor_primary(fixed));
}

static guint find_monitor_point(guint x, guint y)
{
	struct wm_rect mon;
	RECT_SET(mon, x, y, 1, 1);
	return screen_find_monitor(&mon);
}

guint screen_monitor_pointer()
{
	gint x, y;
	if(!screen_pointer_pos(&x, &y))
		x = y = 0;
	return find_monitor_point(x, y);
}

gboolean screen_pointer_pos(gint *x, gint *y)
{
	Window w;
	gint i;
	guint u;
	gboolean ret;
	ret = ! !XQueryPointer(t_display, DefaultRootWindow(t_display), &w, &w, x, y, &i, &i, &u);
	if(!ret) {
		for(i = 0; i < ScreenCount(t_display); ++i)
			if(i != DefaultScreen(t_display))
				if(XQueryPointer(t_display, t_root(i), &w, &w, x, y, &i, &i, &u))
					break;
	}
	return ret;
}

gboolean screen_compare_desktops(guint a, guint b)
{
	if(a == DESKTOP_ALL)
		a = screen_desktop;
	if(b == DESKTOP_ALL)
		b = screen_desktop;
	return a == b;
}
